﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Rhino.Mocks;
using System.IO;
using NSsh.Common.Utility;
using NSsh.Server.TransportLayer.State;
using NSsh.Server.TransportLayer;
using NUnit.Framework.SyntaxHelpers;
using NSsh.Common.Packets;
using NSsh.Common;
using NSsh.Server.Configuration;
using System.Threading;

namespace NSsh.Server.Tests.TransportLayer
{
    [TestFixture]
    public class TransportLayerManagerFixture
    {
        MockRepository _mockRepository;
        Stream _stream;
        AbstractTransportState _connectedState;
        TransportLayerManager _manager;
        NSshServiceConfiguration _config;

        [SetUp]
        public void Setup()
        {
            _mockRepository = new MockRepository();
            _stream = _mockRepository.CreateMock<Stream>();
            _connectedState = _mockRepository.CreateMock<AbstractTransportState>();
            _config = new NSshServiceConfiguration();

            Dependency.Clear();
            Dependency.RegisterInstance<AbstractTransportState>(TransportLayerState.Connected.ToString(), _connectedState);
            Dependency.RegisterInstance<NSshServiceConfiguration>("NSshServiceConfiguration", _config);
        }

        [Test]
        public void ShouldProcessCorrectly()
        {
            using (_mockRepository.Record())
            {
                _connectedState.Process(_manager);
                LastCall.Do(new MethodInvoker<Stream>(delegate
                {
                    _manager.Disconnect(DisconnectReason.CompressionError);
                }));

                ExpectDisconnect(DisconnectReason.CompressionError);
            }

            using (_mockRepository.Playback())
            {
                _manager = new TransportLayerManager();
                _manager.Process(_stream);
            }
        }

        [Test]
        public void ShouldDisconnect()
        {
            using (_mockRepository.Record())
            {
                _connectedState.Process(_manager);
                LastCall.Do(new MethodInvoker<Stream>(delegate
                    {
                        _manager.Disconnect(DisconnectReason.CompressionError);
                    }));

                ExpectDisconnect(DisconnectReason.CompressionError);
            }

            using (_mockRepository.Playback())
            {
                _manager = new TransportLayerManager();
                _manager.Process(_stream);
            }
        }

        [Test]
        public void ShouldTimeout()
        {
            _config.IdleTimeout = TimeSpan.FromSeconds(0.1);

            using (_mockRepository.Record())
            {
                _connectedState.Process(_manager);
                LastCall.Do(new MethodInvoker<Stream>(delegate
                {
                    Thread.Sleep(_config.IdleTimeout + _config.IdleTimeout);
                    _manager.Disconnect(DisconnectReason.CompressionError);
                }));

                ExpectDisconnect(DisconnectReason.CompressionError);
            }

            using (_mockRepository.Playback())
            {
                bool eventCalled = false;

                _manager = new TransportLayerManager();
                _manager.OnIdleTimeout += delegate { eventCalled = true; };
                _manager.StartIdleTimeout(_config.IdleTimeout);
                _manager.Process(_stream);

                Assert.That(eventCalled, Is.True);
            }
        }

        private void ExpectDisconnect(DisconnectReason reason)
        {
            _stream.Write(null, 0, 0);
            LastCall.IgnoreArguments().Do(new WriteBytesInvoker(delegate(byte[] data, int offset, int length)
                {
                    MemoryStream buffer = new MemoryStream(data, offset, length);
                    DisconnectPacket disconnect = new DisconnectPacket(new SshPacketContext(new BinaryReader(buffer)));

                    Assert.That(disconnect.DisconnectReason, Is.EqualTo(reason));
                }));
        }

        delegate void WriteBytesInvoker(byte[] data, int offset, int length);
    }
}
